package eval

import (
	"showen/num"
	"showen/obj"
)

// IsVectorProc checks if an object is a vector
func IsVectorProc(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "vector? requires exactly 1 argument"}
	}

	return &obj.BooleanObject{Value: args[0].Type() == obj.VECTOR_OBJ}
}

// MakeVector creates a new vector
func MakeVector(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) < 1 || len(args) > 2 {
		return &Error{Message: "make-vector requires 1 or 2 arguments"}
	}

	// First argument must be a number
	if args[0].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "make-vector first argument must be a number"}
	}

	k := args[0].(*obj.NumberObject).Value

	// Check if the number is an integer
	if k.Type() != num.INTEGER_TYPE {
		return &Error{Message: "make-vector first argument must be a non-negative integer"}
	}

	kInt := int(k.(num.Integer))
	if kInt < 0 {
		return &Error{Message: "make-vector first argument must be non-negative"}
	}

	// Create vector with k elements
	elements := make([]obj.Object, kInt)

	// If second argument is provided, fill all elements with it
	var fill obj.Object = obj.NULL_OBJECT // default fill value
	if len(args) == 2 {
		fill = args[1]
	}

	for i := 0; i < kInt; i++ {
		elements[i] = fill
	}

	return &obj.VectorObject{Elements: elements}
}

// Vector creates a new vector from the given objects
func Vector(args []obj.Object, env *obj.Environment) obj.Object {
	// Create a copy of the arguments as vector elements
	elements := make([]obj.Object, len(args))
	copy(elements, args)
	return &obj.VectorObject{Elements: elements}
}

// VectorLength returns the length of a vector
func VectorLength(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "vector-length requires exactly 1 argument"}
	}

	if args[0].Type() != obj.VECTOR_OBJ {
		return &Error{Message: "vector-length argument must be a vector"}
	}

	vector := args[0].(*obj.VectorObject)
	return &obj.NumberObject{Value: num.Integer(int64(len(vector.Elements)))}
}

// VectorRef returns the element at the specified index in a vector
func VectorRef(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "vector-ref requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.VECTOR_OBJ {
		return &Error{Message: "vector-ref first argument must be a vector"}
	}

	if args[1].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "vector-ref second argument must be a number"}
	}

	vector := args[0].(*obj.VectorObject)
	index := args[1].(*obj.NumberObject).Value

	// Check if the index is an integer
	if index.Type() != num.INTEGER_TYPE {
		return &Error{Message: "vector-ref index must be a non-negative integer"}
	}

	indexInt := int(index.(num.Integer))
	if indexInt < 0 || indexInt >= len(vector.Elements) {
		return &Error{Message: "vector-ref index out of bounds"}
	}

	return vector.Elements[indexInt]
}

// VectorSet modifies the element at the specified index in a vector
func VectorSet(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 3 {
		return &Error{Message: "vector-set! requires exactly 3 arguments"}
	}

	if args[0].Type() != obj.VECTOR_OBJ {
		return &Error{Message: "vector-set! first argument must be a vector"}
	}

	if args[1].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "vector-set! second argument must be a number"}
	}

	vector := args[0].(*obj.VectorObject)
	index := args[1].(*obj.NumberObject).Value

	// Check if the index is an integer
	if index.Type() != num.INTEGER_TYPE {
		return &Error{Message: "vector-set! index must be a non-negative integer"}
	}

	indexInt := int(index.(num.Integer))
	if indexInt < 0 || indexInt >= len(vector.Elements) {
		return &Error{Message: "vector-set! index out of bounds"}
	}

	// Set the element at the specified index
	vector.Elements[indexInt] = args[2]

	// vector-set! returns an unspecified value, we'll use NULL_OBJECT
	return obj.NULL_OBJECT
}

// VectorToList converts a vector to a list
func VectorToList(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "vector->list requires exactly 1 argument"}
	}

	if args[0].Type() != obj.VECTOR_OBJ {
		return &Error{Message: "vector->list argument must be a vector"}
	}

	vector := args[0].(*obj.VectorObject)

	// Convert vector elements to a list (in reverse order first, then reverse)
	var result obj.Object = obj.EMPTY_LIST
	for i := len(vector.Elements) - 1; i >= 0; i-- {
		result = &obj.PairObject{Car: vector.Elements[i], Cdr: result}
	}

	return result
}

// ListToVector converts a list to a vector
func ListToVector(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 1 {
		return &Error{Message: "list->vector requires exactly 1 argument"}
	}

	// Check if the argument is a list (pair or empty list)
	if args[0].Type() != obj.PAIR_OBJ && args[0].Type() != obj.EMPTYLIST_OBJ && args[0].Type() != obj.NULL_OBJ {
		return &Error{Message: "list->vector argument must be a list"}
	}

	// Convert list to vector elements
	elements := make([]obj.Object, 0)

	// Handle empty list
	if args[0] == obj.EMPTY_LIST || args[0].Type() == obj.NULL_OBJ {
		return &obj.VectorObject{Elements: elements}
	}

	current := args[0]
	for current.Type() == obj.PAIR_OBJ {
		pair := current.(*obj.PairObject)
		elements = append(elements, convertToObjObject(pair.Car))

		// Move to the next element
		if pair.Cdr == obj.EMPTY_LIST || pair.Cdr == obj.NULL_OBJECT {
			break
		}

		if pair.Cdr.(*obj.PairObject) == nil {
			return &Error{Message: "list->vector argument must be a proper list"}
		}

		current = convertToObjObject(pair.Cdr)
	}

	return &obj.VectorObject{Elements: elements}
}

// VectorFill fills a vector with a given value
func VectorFill(args []obj.Object, env *obj.Environment) obj.Object {
	if len(args) != 2 {
		return &Error{Message: "vector-fill! requires exactly 2 arguments"}
	}

	if args[0].Type() != obj.VECTOR_OBJ {
		return &Error{Message: "vector-fill! first argument must be a vector"}
	}

	vector := args[0].(*obj.VectorObject)
	fill := args[1]

	// Fill all elements with the given value
	for i := range vector.Elements {
		vector.Elements[i] = fill
	}

	// vector-fill! returns an unspecified value, we'll use NULL_OBJECT
	return obj.NULL_OBJECT
}
